iT邦幫忙

2025 iThome 鐵人賽

DAY 7
1
Software Development

30 天 Effective C++ 大挑戰!!系列 第 7

[Day 7] Designs and Declarations I

  • 分享至 

  • xImage
  •  

終於進到第四章 —— Designs and Declarations!許多概念都和前面的内容相關聯,建議先簡單複習後再次出發。

18. Make interfaces easy to use correctly and hard to use incorrectly

良好的 interface 設計應保持一致性,並讓行為與內建型別相容。假設我們設計一個名為 YoyoBirthday 的 class,它的建構函數需要傳入年月日。直接用三個 int 參數當作「年」、「月」、「日」容易混淆,更好的做法是分別定義參數的專屬型別。

此外,可以在「月」的 class 裡提供類似 January() 的靜態函式,而不是直接用 enumstatic const

其他防止參數傳遞錯誤的方法包括:

  • 限制型別的操作能力:將運算結果的回傳值宣告為 const,或盡可能使用 const 修飾函式。
  • 與內建型別保持一致的行為。 e.g. 自定義類的 operator+ 應與數學加法的行為一致。
  • 移除資源管理的所有權:返回 std::shared_ptr 等智慧指標以自動管理資源,避免讓使用者手動 delete 釋放資源。

19. Treat class design as type design

設計 class 其實就是設計 type。在定義新型別之前,務必考慮以下所有關鍵問題:

  • 如何創建和銷毀物件? 這決定了如何設計 constructor 和 destructor,以及相關的記憶體分配。
  • 「初始化」和「賦值」有何不同? 初始化是透過 constructor 實現,而賦值涉及 copy assignment operator。
  • Pass-by-value 會發生什麼? 會調用 copy constructor,此過程應該符合預期。
  • Object 的有效取值範圍? 根據有效值範圍,可在建構函數、setter 方法或其他成員函數中檢查數值是否合理。
  • 繼承關係的處理? 如果會產生衍生類別,基底類別需要設計虛函數;如果是衍生類別,則需要實現基底類別的虛函數。
  • Class的型別轉換能力? 設計類型轉換函數時需考慮是隱式還是顯式。顯式通常更為安全。
  • 哪些函數或運算符對類有意義? 這決定了需要實現哪些操作,例如 += 或比較運算符。
  • 哪些編譯器生成的函數需要禁用? 若不希望編譯器生成某些函數,可聲明為 deletedefault
  • 成員訪問權限如何設計? 根據需求設計為 publicprotectedprivate,並考慮是否需要 friend function。
  • 是否存在未宣告的隱式轉換? Class 的隱式行為,如性能、例外處理、資源使用等都需要考量。
  • Class 的泛型化能力如何? 需考慮如何正確設計 template argument。
  • 是否真的需要 class? 有些情況下用幾個 function 即可替代,應避免不必要的設計。

20. Prefer pass-by-reference-to-const to pass-by-value

Reference 支持多型,可以正確傳遞基底類別和衍生類別的物件。若對上述名詞感到陌生,可以參考知識點 7知識點 9

通常偏好以 pass-by-reference-to-const 而非 pass-by-value 傳遞,因為它通常更高效且能避免 slicing 問題。Pass-by-value 會透過 copy constructor 進行數值和物件的複製,結束後還需浪費額外的性能調用 destructor。使用 const 引用可以避免這些操作。

此規則不適用於內建型別以及 STL 的 iterator 和 function object 類型。

21. Don't try to return a reference when you must return an object

雖然在函數的參數傳遞中用 reference 通常效率更高,但在 return 中直接返回 reference 往往會引發問題!

  • 返回 local object 的 reference:如果函式創建的是 stack 上的 local object,當函式結束時此物件就會被銷毀。返回其 reference 將指向已不存在的物件,導致未定義行為。
  • 返回 new 產出的 heap object 的 reference:雖然理論上可行,但產生的物件需要手動管理,否則容易導致資源泄漏。
const Rational& operator*(const Rational& lhs, const Rational& rhs) {
    Rational* result = new Rational(lhs.n * rhs.n, lhs.d * rhs.d);
    return *result;
}
  • 返回 static object 的 reference:靜態物件雖然在 scope 外仍然有效,但它會導致多執行緒競爭問題,且多次調用會共享同一個靜態物件導致邏輯錯誤。
const Rational& operator*(const Rational& lhs, const Rational& rhs) {
    static Rational result;
    result = Rational(lhs.n * rhs.n, lhs.d * rhs.d);
    return result;
}

總結來說,理想的方式是直接返回一個新的物件 value,並依靠編譯器的運算優化來減少性能損耗。同時,value 有清晰的所有權,不會影響使用者對資源的管理。


上一篇
[Day 6] Resource Management
下一篇
[Day 8] Design and Declarations II
系列文
30 天 Effective C++ 大挑戰!!30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言